package com.tfswx.jcyw.dmzc.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.tfswx.jcyw.dmzc.dto.Api;
import fi.iki.elonen.NanoHTTPD;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * @author ricky
 * @date 2023/3/13
 */
@Slf4j
public class HttpServer extends NanoHTTPD {


    private final Map<String, Api> apiMap;

    private final Function<String, byte[]> loadLocalResource;

    public HttpServer(int port, Map<String, Api> apiMap, Function<String, byte[]> loadLocalResource) {
        super( port);
        this.apiMap = apiMap;
        this.loadLocalResource = loadLocalResource;
    }

    @Override
    public Response serve(IHTTPSession session) {
        long start = System.currentTimeMillis();
        try {
            if (isPreflightRequest(session)) {
                return responseCORS(session);
            }
            Response response = dealWith(session);
            return wrapResponse(session, response);
        } finally {
            log.info("<== {} {} {}ms", session.getUri(), session.getMethod(), System.currentTimeMillis() - start);
        }
    }

    /**
     * 判断是否为CORS 预检请求请求(Preflight)
     *
     * @param session
     * @return
     */
    private static boolean isPreflightRequest(IHTTPSession session) {
        Map<String, String> headers = session.getHeaders();
        return Method.OPTIONS.equals(session.getMethod())
                && headers.containsKey("origin")
                && headers.containsKey("access-control-request-method")
                && headers.containsKey("access-control-request-headers");
    }

    /**
     * 向响应包中添加CORS包头数据
     *
     * @param session
     * @return
     */
    private Response responseCORS(IHTTPSession session) {
        Response resp = wrapResponse(session, newFixedLengthResponse(""));
        Map<String, String> headers = session.getHeaders();
        resp.addHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");

        String requestHeaders = headers.get("access-control-request-headers");
        String allowHeaders = StringUtils.isNotBlank(requestHeaders) ? requestHeaders : "Content-Type";
        resp.addHeader("Access-Control-Allow-Headers", allowHeaders);
        // resp.addHeader("Access-Control-Max-Age", "86400");
        resp.addHeader("Access-Control-Max-Age", "0");
        return resp;
    }

    /**
     * 封装响应包
     *
     * @param session http请求
     * @param resp    响应包
     * @return resp
     */
    private Response wrapResponse(IHTTPSession session, Response resp) {
        if (null != resp) {
            resp.setMimeType("text/html;charset=UTF-8");
            Map<String, String> headers = session.getHeaders();
            resp.addHeader("Access-Control-Allow-Credentials", "true");
            // 如果请求头中包含'Origin',则响应头中'Access-Control-Allow-Origin'使用此值否则为'*'
            // nanohttd将所有请求头的名称强制转为了小写
            String origin = headers.get("origin");
            resp.addHeader("Access-Control-Allow-Origin", StringUtils.isNotBlank(origin) ? origin : "*");

            String requestHeaders = headers.get("access-control-request-headers");
            if (requestHeaders != null) {
                resp.addHeader("Access-Control-Allow-Headers", requestHeaders);
            }
        }
        return resp;
    }

    @SneakyThrows
    private Response dealWith(IHTTPSession session) {
        // 日志输出外部请求相关的日志信息
        Map<String, String> headers = session.getHeaders();
        String uri = session.getUri();
        Method method = session.getMethod();

        Api api = apiMap.get(uri);
        if (api == null) {
            byte[] apply = loadLocalResource.apply(uri);
            if (apply != null) {
                return newChunkedResponse(Response.Status.OK, null, new ByteArrayInputStream(apply));
            }
            return newChunkedResponse(Response.Status.NOT_FOUND, null, null);
        }
        try {
            // 是接口
            if (!api.getMethod().equals(method.name().toLowerCase())) {
                // 接口与请求类型不匹配
                throw new RuntimeException("请求类型不匹配，不支持" + method.name());
            }
            String paramStr = "";
            if ("post".equals(api.getMethod())) {
                Map<String, String> files = new HashMap<>();
                session.parseBody(files);
                paramStr = files.get("postData");
            } else if ("get".equals(api.getMethod())) {
                Map<String, String> parms = session.getParms();
                parms.put("NanoHttpd.QUERY_STRING", session.getQueryParameterString());
            }

            java.lang.reflect.Method m = api.getM();
            Class<?>[] parameterTypes = m.getParameterTypes();
            Type[] genericParameterTypes = m.getGenericParameterTypes();

            Object o = api.getO();
            Object[] params = new Object[parameterTypes.length];
            for (int i = 0; i < genericParameterTypes.length; i++) {
                params[i] = JSON.parseObject(paramStr, genericParameterTypes[i]);
            }
            Object data = m.invoke(o, params);

            return responseJsonString(0, data, "请求成功");
        } catch (Throwable t) {
            Throwable throwable = getThrowable(t);
            log.error("请求失败，接口：{}", uri, throwable);
            return responseJsonString(-1, null, throwable.getMessage());
        }
    }

    private <T> Response responseJsonString(int code, T data, String message) {
        Responser<T> responser = new Responser<>();
        responser.setCode(code);
        responser.setData(data);
        responser.setMessage(message);
        return newFixedLengthResponse(JSON.toJSONString(responser, SerializerFeature.WriteMapNullValue));
    }

    @Data
    static class Responser<T> {
        private int code;
        private T data;
        private String message;
    }

    private static Throwable getThrowable(Throwable t) {
        if (t == null) {
            return null;
        }
        if (t.getCause() == null) {
            return t;
        }
        if (t instanceof InvocationTargetException) {
            return getThrowable(t.getCause());
        } else if (t instanceof UndeclaredThrowableException) {
            return getThrowable(t.getCause());
        } else {
            return t;
        }
    }
}

